#!/usr/bin/python
#coding=utf-8
# -----------------------------------------------------------------------------
# calc.py
#
# A simple calculator with variables.   This is from O'Reilly's
# "Lex and Yacc", p. 63.
#
# Class-based example contributed to PLY by David McNab.
#
# Modified to use new-style classes.   Test case.
# -----------------------------------------------------------------------------

import sys
#sys.path.insert(0,"../..")

import ply.lex as lex
import ply.yacc as yacc
import os

class CalcError(Exception):
    def __init__(self, value):
      self.value = value
    def __str__(self):
        return repr(self.value)

class Parser(object):
    """
    Base class for a lexer/parser that has the rules defined as methods
    """
    tokens = ()
    precedence = ()

    def __init__(self):
        lex.lex(module=self)
        yacc.yacc(module=self, debug = 0)

    def run(self, s, d):
        self.names = d
        yacc.parse(s)
 
class Calc(Parser):

    tokens = (
        'NAME','NUMBER', #'FUNCTION',
        'PLUS','MINUS', 'TIMES','DIVIDE',
        'LPAREN','RPAREN',
        )

    # Tokens

    t_PLUS    = r'\+'
    t_MINUS   = r'-'
    t_TIMES   = r'\*'
    t_DIVIDE  = r'/'
    t_LPAREN  = r'\('
    t_RPAREN  = r'\)'
    t_NAME    = r'[a-zA-Z_][a-zA-Z0-9_]*[:]?[a-zA-Z0-9_]*'

    def t_NUMBER(self, t):
        r'\d+[.,]?\d*'
        #t.value = str(t.value)
        #print t.value
        t.value = t.value.replace(',','.')
        #print t.value
        try:
            t.value = float(t.value)
        except ValueError:
            raise CalcError("Integer value too large %s" % t.value)
        return t

    t_ignore = " \t"

    def t_newline(self, t):
        r'\n+'
        t.lexer.lineno += t.value.count("\n")
    
    def t_error(self, t):
        raise CalcError("Illegal character '%s'" % t.value[0])
        #print("Illegal character '%s'" % t.value[0])
        #t.lexer.skip(1)

    # Parsing rules

    precedence = (
        ('left','PLUS','MINUS'),
        ('left','TIMES','DIVIDE'),
        ('right','UMINUS'),
        )

    def p_statement_expr(self, p): 
        'statement : expression'
        self.res = str(p[1])

    def p_expression_binop(self, p):
        """
        expression : expression PLUS expression
                  | expression MINUS expression
                  | expression TIMES expression
                  | expression DIVIDE expression
        """
        #print [repr(p[i]) for i in range(0,4)]
        if p[2] == '+'  : p[0] = p[1] + p[3]
        elif p[2] == '-': p[0] = p[1] - p[3]
        elif p[2] == '*': p[0] = p[1] * p[3]
        elif p[2] == '/': p[0] = p[1] / p[3]

    def p_expression_uminus(self, p):
        'expression : MINUS expression %prec UMINUS'
        p[0] = -p[2]

    def p_expression_group(self, p):
        'expression : LPAREN expression RPAREN'
        p[0] = p[2]

    def p_expression_number(self, p):
        'expression : NUMBER'
        p[0] = p[1]

    def p_expression_name(self, p):
        'expression : NAME'
        try:
            p[0] = self.names[p[1]]
        except LookupError:
            raise CalcError("Undefined name '%s'" % p[1])
            #print("Undefined name '%s'" % p[1])
            #p[0] = 0

    def p_error(self, p):
        if p:
            raise CalcError("Syntax error at '%s'" % p.value)
            #print("Syntax error at '%s'" % p.value)
        else:
            raise CalcError("Syntax error at EOF")
            #print("Syntax error at EOF")

def formula(s, d):
    calc = Calc()
    s = str(s)
    for t in d:
        try:
            d[t] = float(d[t])
        except:
            pass # Значит там была строковая, она останется строкой    
    if s=="":
        return ""
    calc.run(s, d)
    return calc.res

if __name__ == '__main__':
    print formula("1+2", {'x':3})
    #print p[1]
